
#include <QDebug>
#include <QGLShader>
#include <QPainter>
#include <QEvent>
#include <QTouchEvent>
#include <QGraphicsSceneMouseEvent>
#include <QtGui/qdrawutil.h>
#include <QVarLengthArray>
#include <QtCore/qmath.h>
#include <QtCore/qglobal.h>
#include <QVector2D>

#include "qparticles.h"

#include "vertexpositioncolor.h"

#define KLIMITTOPAGE

#define KDEFAULTPARTICLES 100

const QVector2D t1(2.0f, 0.0f);
const QVector2D t2(0.0f, 2.0f);


QParticles::QParticles(QDeclarativeItem *parent) :
    QDeclarativeItem(parent), clock(this), m_lastTickTime(0), m_upTime(0), m_particles(0), data(0),
    m_colors(KCOLORS), m_InitShader(false),iColorInit(false), iParticleInit(false), m_particleSize(2.0f),
    m_SquareParticle(true), m_BaseShader(NULL)
{
    qDebug() << "QParticles::QParticles-> new:" << this;
    setFlag(QGraphicsItem::ItemHasNoContents, false);
    setAcceptedMouseButtons(Qt::LeftButton);
    setAcceptTouchEvents(true);
    setFiltersChildEvents(true);

    generateParticles(KDEFAULTPARTICLES);

    qDebug() << "QParticles::QParticles<-";
}


void QParticles::generateColors()
{
    if(iColorInit)
    {
        return;
    }
    iColorInit = true;
    int cnt = m_colors.count();
    QColor color;
    for(int i=0; i<cnt; ++i)
    {
        color.setHslF((qreal)i / KCOLORS, 1.0, 0.50);
        m_colors[i].setX(color.red()/255.0);
        m_colors[i].setY(color.green()/255.0);
        m_colors[i].setZ(color.blue()/255.0);
    }
}
void QParticles::generateParticles( int aNewParticleCount)
{
//    qDebug() << "->generateParticles";
    m_particles.resize(aNewParticleCount);
    if(m_SquareParticle)
    {
        data.resize(aNewParticleCount*6);
    }
    else
    {
        data.resize(aNewParticleCount*3);
    }
    iParticleInit = false;
//    qDebug() << "<-generateParticles";
}

void QParticles::generateParticles(const QRectF & boundingRect)
{
//    qDebug() << "generateParticles()" << iParticleInit <<  "particlescount:" << m_particles.count() << "" << &m_particles ;
    if(iParticleInit)
    {
        return;
    }

    iParticleInit = true;
    int cnt = m_particles.count();
    for(int i=0; i<cnt; ++i)
    {
        QVector2D & pos(m_particles[i].Position);
        pos.setX( randInt(0, boundingRect.width()));
        pos.setY( randInt(0, boundingRect.height()));
    }
}

//void QParticles::geometryChanged(const QRectF &newGeometry,
//                             const QRectF &oldGeometry)
//{

//    qDebug() << "QParticles::geometryChanged->";
//    qDebug() << "old:" << oldGeometry;
//    qDebug() << "new:" << newGeometry;
//    QDeclarativeItem::geometryChanged(newGeometry, oldGeometry);
//    qDebug() << "QParticles::geometryChanged<-";
//}

void QParticles::componentComplete()
{
    qDebug() << "QParticles::componentComplete->";
    QDeclarativeItem::componentComplete();

    qDebug() << boundingRect();

    qDebug("[%f,%f] [%fx%f] opacity:%f Active:%d Visible:%d", x(), y(), width(), height(), opacity(), isActive(), isVisible());


    QBrush m_color(QColor(255, 255, 255, 255), Qt::SolidPattern);
    m_pen =  QPen(m_color, 2);


    startRedraw();
//    qDebug() << "QParticles::componentComplete<-";
}

bool QParticles::updateParticles(qreal deltaTime, const QRectF & boundingRect, bool dontNull)
{
    QPointF TL(boundingRect.topLeft());
    QPointF BR(boundingRect.bottomRight());
    int cnt = m_particles.count();
    bool velocity(false);
    QVector2D a;
    for (int i = 0; i < cnt; ++i)
    {
        QParticle & p = m_particles[i];

        a = p.Force * p.InvertMass;
        p.Velocity += a * deltaTime;
        p.Velocity = p.Velocity * (qreal)0.96;

        p.Position += p.Velocity * deltaTime;

        if (p.Position.x() < TL.x())
        {
            p.Velocity.setX(-p.Velocity.x());
            p.Position.setX(TL.x());
        }
        else if ( p.Position.x() > BR.x())
        {
            p.Velocity.setX(-p.Velocity.x());
            p.Position.setX(BR.x());
        }

        if (p.Position.y() < TL.y())
        {
            p.Velocity.setY(-p.Velocity.y());
            p.Position.setY(TL.y());
        }
        else if (p.Position.y() > BR.y())
        {
            p.Velocity.setY(-p.Velocity.y());
            p.Position.setY(BR.y());
        }

        p.Force.setX((qreal)0.0);
        p.Force.setY((qreal)0.0);

        if((m_touchpos.count() == 0) && !p.zeroVelocity(dontNull))
        {
            velocity = true;
//            qDebug() << i << p.Velocity;
        }

//        m_drawParticles[i] = p.Position.toPointF();
    }
    return velocity;
}




void QParticles::applyTouchGravity(const QPointF & center)
{
    int cnt = m_particles.count();

    QVector2D dir;
    QVector2D force;
    float distSq;
    float dist;
    qreal forceSize;
    for (int i = 0; i < cnt; ++i)
    {
        QParticle &  p(m_particles[i]);
        dir.setX(center.x() - p.Position.x());
        dir.setY(center.y() - p.Position.y());

//        qreal distSq = (qreal)1.0 / (dir.x() * dir.x() + dir.y() * dir.y());
//        qreal dist = qSqrt(distSq);

        distSq = 1.0f/(dir.x() * dir.x() + dir.y() * dir.y());
//        qreal distSq = (dir.x() * dir.x() + dir.y() * dir.y());
        dist = qSqrt(distSq);
//        qreal dist = Q_rsqrt(distSq);

        dir *= dist;

//        qreal forceSize = (qreal)500.0 * qMin((qreal)1.0, ((qreal)5000.0 * distSq));
        forceSize = 500.0f * qMin(1.0f, (6000.0f * distSq));
        force.setX(dir.x() * forceSize);
        force.setY(dir.y() * forceSize);
        {
            p.Force += force;
        }
    }
}

//bool QParticles::sceneEvent(QEvent *event)
//{
//    bool rv = QDeclarativeItem::sceneEvent(event);
//    if (event->type() == QEvent::UngrabMouse) {
//        setKeepMouseGrab(false);
//    }
//    return rv;
//}

//bool QParticles::event(QEvent *event)
//{
//    if (!isVisible())
//        return QDeclarativeItem::event(event);
//    switch (event->type()) {
//    case QEvent::TouchBegin:
//    case QEvent::TouchUpdate: {
//            QTouchEvent *touch = static_cast<QTouchEvent*>(event);
//                        const QList<QTouchEvent::TouchPoint> & touchPoints(touch->touchPoints());
//                        {

//                            int count = touchPoints.count();
//                            qDebug() << count << "----------------------------------";
//                            for(int i=0; i<count; ++i)
//                            {
//                                qDebug("[%d] id[%d][%d,%d] state:%x", i, touchPoints[i].id(), (int)touchPoints[i].pos().x(),
//                                       (int)touchPoints[i].pos().y(), (int)touchPoints[i].state() );


//                            }
//                        }
////            d->touchPoints.clear();
////            for (int i = 0; i < touch->touchPoints().count(); ++i) {
////                if (!(touch->touchPoints().at(i).state() & Qt::TouchPointReleased)) {
////                    d->touchPoints << touch->touchPoints().at(i);
////                }
////            }
////            updatePinch();
//        }
//        return true;
//    case QEvent::TouchEnd:
//            qDebug() << "QEvent::TouchEnd";
////        d->touchPoints.clear();
////        updatePinch();
//        break;
//    default:
//        return QDeclarativeItem::event(event);
//    }

//    return QDeclarativeItem::event(event);
//}

bool QParticles::sceneEvent(QEvent *event)
{
    QEvent::Type eventType(event->type());
    switch (eventType)
    {
        case QEvent::TouchBegin:
        case QEvent::TouchUpdate:
        case QEvent::TouchEnd:
        {
            startRedraw();
            const QList<QTouchEvent::TouchPoint> touchPoints(static_cast<QTouchEvent *>(event)->touchPoints());
//            {

//                int count = touchPoints.count();
//                qDebug() << count << "QEvent::Touch----------------------------------";
//                for(int i=0; i<count; ++i)
//                {
//                    qDebug("[%d] id[%d][%d,%d] state:0x%x", i, touchPoints[i].id(), (int)touchPoints[i].pos().x(),
//                           (int)touchPoints[i].pos().y(), (int)touchPoints[i].state() );

//                }
//            }
            m_touchpos.clear();


            foreach(const QTouchEvent::TouchPoint touchPoint, touchPoints)
            {
                if( touchPoint.state() != Qt::TouchPointReleased)
                {
                    m_touchpos.append(touchPoint.pos());
                }

            }



            if(m_touchpos.count() == 0)
            {
                m_upTime = m_lastTickTime;
///                qDebug() << "----- empty";
            }
            else
            {
//                qDebug() << "-----" << m_touchpos.count();
            }
            return true;
        }
#if defined(Q_WS_WIN)
        case QEvent::GraphicsSceneMousePress:
        case QEvent::GraphicsSceneMouseMove:
        {
            startRedraw();
            QGraphicsSceneMouseEvent * MouseEvent = static_cast<QGraphicsSceneMouseEvent *>(event);
            m_touchpos.clear();
            qDebug() << "QEvent::GraphicsSceneMouseMove";
//            qDebug( "Pos[%f,%f] ScenePos[%f,%f] screenPos[%f,%f]", MouseEvent->pos().x(), MouseEvent->pos().y(),
//                    MouseEvent->scenePos().x(), MouseEvent->scenePos().y(), MouseEvent->screenPos().x(), MouseEvent->screenPos().y()
//                    );
            m_touchpos.append(MouseEvent->pos());
//            qDebug() << MouseEvent->pos();
            return true;
        }

        case QEvent::GraphicsSceneMouseRelease:
        {
            m_upTime = m_lastTickTime;
            m_touchpos.clear();
            qDebug() << "QEvent::GraphicsSceneMouseRelease ----- empty";
            return true;
        }
#endif
        default:
        {
//            qDebug() << "eventType:" << eventType;
        }
    }
    return QDeclarativeItem::sceneEvent(event);
}

void QParticles::tick(int time)
{
    if(m_lastTickTime == 0)
    {
        m_lastTickTime = time;
        return;
    }

    int diff = time - m_lastTickTime;
//    qDebug() << "diff:" << diff;
    if(diff < 20)
    {
        return;
    }

    qreal elapsedTimeS = qreal(diff) / qreal(1000.0);
    //qreal elapsedTimeS = 33.0f / 1000.0f;
//    qDebug() << "diff:" << (time - m_lastTickTime);
    //<< "elapsed:" << elapsedTimeMs;
    {
        // touch gravity
        int touchCount = m_touchpos.count();
        for(int i=0; i< touchCount; ++i)
        {
//            qDebug() << "[" << i << "]" << m_touchpos[i];
            applyTouchGravity(m_touchpos[i]);
        }
//        if(touchCount == 0)
//        {
//            qDebug() << "tick touch points empty";
//        }
    }

    int sinceKeyUp = time - m_upTime;
    bool dontnull((sinceKeyUp < 1000) || m_touchpos.count());

    QRectF boundingrect;
//    if(m_DrawOnUI)
//    {
//        boundingrect = topLevelItem()->boundingRect();
//    }
//    else
    {
        boundingrect = boundingRect();
    }
    boundingrect.setHeight(boundingrect.height() - m_particleSize);

//    qDebug() << "OnUi:" << m_DrawOnUI << " boundingrect: "  << boundingrect;
//#ifdef KLIMITTOPAGE
//    boundingrect.translate(scenePos());
//#endif
//    qDebug() << "boundingrect:" << boundingrect;
    bool velocity = updateParticles(elapsedTimeS, boundingrect, dontnull);
    if(!dontnull && !velocity)
    {
        stopRedraw();
        qDebug() << "stoping";
    }


    update();

    m_lastTickTime = time;
}

int QParticles::randInt(int low, int high) const
{
    // Random number between low and high
    return qrand() % ((high + 1) - low) + low;
}

void QParticles::onActivated()
{
    qDebug() << "QParticles::onActivated->";
    qDebug("[%f,%f] [%fx%f] opacity:%f Active:%d Visible:%d", x(), y(), width(), height(), opacity(), isActive(), isVisible());
    qDebug() << "QParticles::onActivated<-";


}

void QParticles::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
{

//    qDebug() << "paint: opacity:" << painter->opacity() << " active:" << isActive();
//    qDebug() << "m_touchpos" << &m_touchpos << " m_particles:" << &m_particles << "&m_MatrixLoc" << &m_MatrixLoc << "this" << this;
    //<< " matrix:" << painter->matrix();

    qDebug("pos[%f,%f] size[%fx%f] opacity:%f Active:%d Visible:%d scene[%f,%f]", x(), y(),
           width(), height(), opacity(), isActive(), isVisible(), scenePos().x(), scenePos().y());

    //qDebug() << "viewport:" << painter->viewport() << " window:" << painter->window();
//    qDebug() << "wt" << scenePos() << " >> " << painter->worldTransform().map(scenePos());
//    qDebug() << "tr"  << scenePos() << " >> " << painter->transform().map(scenePos());
//    qDebug() << "dt"  << scenePos() << " >> " << painter->deviceTransform().map(scenePos());
//    qDebug() << "ct"  << scenePos() << " >> " << painter->combinedTransform().map(scenePos());
//    return;




    // init
    generateColors();
    if(isActive())
    {
        generateParticles(boundingRect());
    }

    painter->save();
    painter->beginNativePainting();

    InitShaders();

    paintParticles(painter->opacity(), 3.0f);

    painter->endNativePainting();

    painter->restore();
}

void QParticles::stopRedraw()
{
    clock.stop();
    m_lastTickTime = 0;
    m_upTime = 0;
    qDebug() << "stopRedraw";
}

void QParticles::startRedraw()
{
    if(clock.state() == QAbstractAnimation::Running)
    {
        return;
    }
    qDebug() << "startRedraw";
    clock.start();
    m_lastTickTime = 0;
    m_upTime = 0;
}


void QParticles::InitShaders()
{
    if(m_InitShader)
    {
        return;
    }

    qDebug() << "this" << this;

    m_InitShader = true;
    qDebug("currentContext:0x%x", QGLContext::currentContext());

//    m_BaseShader.release();
//    m_BaseShader.bind();

    delete m_BaseShader;
    m_BaseShader = new QGLShaderProgram(QGLContext::currentContext(), this);

    QGLShader* vs = new QGLShader(QGLShader::Vertex, QGLContext::currentContext(),  this);

    const char *vssrc =
            "uniform mat4 matrix;\n"
            "attribute vec4 vertex;\n"
            "attribute vec4 color;\n"
            "varying mediump vec4 outColor;\n"
            "void main(void)\n"
            "{\n"
            "   outColor = color;\n"
            "   gl_Position = matrix * vertex;\n"
            "}\n";

    bool done = vs->compileSourceCode(vssrc);
    Q_ASSERT(done);



    QGLShader *fs = new QGLShader(QGLShader::Fragment, QGLContext::currentContext(), this);
    const char *fssrc =
            "varying mediump vec4 outColor;\n"
            "void main(void)\n"
            "{\n"
            "    gl_FragColor = outColor;\n"
            "}\n";

    done = fs->compileSourceCode(fssrc);
    Q_ASSERT(done);


    done = m_BaseShader->addShader(vs);
    if(!done)
    {
        qDebug() << m_BaseShader->log();
    }
    Q_ASSERT(done);
    done = m_BaseShader->addShader(fs);
    if(!done)
    {
        qDebug() << m_BaseShader->log();
    }
    Q_ASSERT(done);
    done = m_BaseShader->link();
    if(!done)
    {
        qDebug() << m_BaseShader->log();
    }
    Q_ASSERT(done);

    m_MatrixLoc = m_BaseShader->uniformLocation("matrix");
    qDebug() << "m_MatrixLoc:" << m_MatrixLoc;
    m_VertexAtribLoc = m_BaseShader->attributeLocation("vertex");
    qDebug() << "m_VertexAtribLoc:" << m_VertexAtribLoc;
    m_ColorAttribLoc = m_BaseShader->attributeLocation("color");
    qDebug() << "m_ColorAttribLoc:" << m_ColorAttribLoc;
}

QVector3D QParticles::convertColor(int aColor) const
{
    return QVector3D(float((aColor & 0xFF0000) >> 16) / 255,
                     float((aColor & 0xFF00)>> 8) / 255,
                     float(aColor & 0xFF) / 255);
}


void QParticles::paintParticles( qreal aOpacity, qreal aParticleSize)
{
    if(!iParticleInit)
    {
        return;
    }
//    qDebug() << "->paintParticles";
//    return;
    // generate vertices
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE );
//    glDisable(GL_CULL_FACE);
//    glEnable(GL_CU);

    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glEnable(GL_BLEND);

//    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // alloc buffer
    int count = m_particles.count();

    // generate data
    QVector4D color;

    QVector3D pos0;
    QVector3D pos1;
    QVector3D pos2;
    QVector3D pos3;
    int index;
    for(int i=0; i<count; ++i)
    {
        //qDebug() << m_particles[i].Position;
        //int len = (int(m_particles[i].FastVelocityLength() + 170) % KCOLORS);
        int len = (int)((m_particles[i].Velocity.length() + 170)) % KCOLORS;
        color =  m_colors[len];
        color.setW(aOpacity);

        if(m_SquareParticle)
        {
            index = 6*i;
            data[index].Color = color;
            data[index+1].Color = color;
            data[index+2].Color = color;
            data[index+5].Color = color;
        }
        else
        {
            index = 3*i;
            data[index].Color = color;
            data[index+1].Color = color;
            data[index+2].Color = color;
        }

        pos3 = pos2 = pos1 = pos0 = m_particles[i].Position;

        if(m_SquareParticle)
        {
            index = 6*i;
            pos1.setX(pos1.x() + m_particleSize);
            pos2.setX(pos2.x() + m_particleSize);
            pos2.setY(pos2.y() + m_particleSize);
            pos3.setY(pos3.y() + m_particleSize);

            data[index].Position = pos0;
            data[index+1].Position = pos1;
            data[index+2].Position = pos2;

            data[index+3] = data[index];
            data[index+4] = data[index+2];
            data[index+5].Position = pos3;
        }
        else
        {
            index = 3*i;
            data[index].Position = pos0;
            pos0.setX(pos0.x() + m_particleSize);
            data[index+1].Position = pos0;
            pos0.setY(pos0.y() + m_particleSize);
            data[index+2].Position = pos0;
        }
    }

    m_BaseShader->bind();


    QMatrix4x4 modelview;

    {
        QRectF boundingRect = topLevelItem()->boundingRect();
        modelview.ortho( 0.0f,
                         boundingRect.width(),
                         boundingRect.height(),
                         0.0f,
                        -100.0f, 100.0f);
        QMatrix4x4 translate;
        translate.translate(scenePos().x() , scenePos().y(), 0);
//        qDebug() << "boundingRect:" << boundingRect;
//        qDebug() << "scenePos:" << scenePos();
        modelview = modelview * translate ;
    }

    m_BaseShader->setUniformValue(m_MatrixLoc, modelview);

    m_BaseShader->enableAttributeArray(m_VertexAtribLoc);
    m_BaseShader->enableAttributeArray(m_ColorAttribLoc);

    m_BaseShader->setAttributeArray(m_VertexAtribLoc, GL_FLOAT, (void*)&(data[0].Position), 3, sizeof(TVertexPositionColor));
    m_BaseShader->setAttributeArray(m_ColorAttribLoc, GL_FLOAT, (void*)&(data[0].Color), 4, sizeof(TVertexPositionColor));

    if(m_SquareParticle)
    {
        glDrawArrays(GL_TRIANGLES, 0, 2*count);
    }
    else
    {
        glDrawArrays(GL_TRIANGLES, 0, count);
    }


    m_BaseShader->disableAttributeArray(m_VertexAtribLoc);
    m_BaseShader->disableAttributeArray(m_ColorAttribLoc);

    m_BaseShader->release();
//    qDebug() << "<-paintParticles";
}

void QParticles::setParticles(int aNewParticlesCount)
{

    qDebug() << "setParticles:" << aNewParticlesCount;
    qDebug() << "this" << this;
    generateParticles(aNewParticlesCount);
}

int QParticles::particles() const
{
    return m_particles.count();
}

void QParticles::setParticleSize(float aParticleSize)
{
    m_particleSize = aParticleSize;
}

float QParticles::particleSize() const
{
    return m_particleSize;
}

void QParticles::appendParticle()
{
    m_particles.append(QParticle());
}

void QParticles::setSquareParticle(bool aSquareParticle)
{
    m_SquareParticle = aSquareParticle;
    generateParticles(m_particles.count());
}

bool QParticles::squareParticle() const
{
    return m_SquareParticle;
}




